import { CancellationToken, ConfigurationTarget, Disposable, Event, Extension, ExtensionContext, Memento, OutputChannel, Uri, WorkspaceEdit } from "vscode";
import { LanguageServerType } from "../activation/types";
import { EQEnvironment } from "../eqEnvironments/info";
import { InstallOptions, InterpreterUri, ModuleInstallFlags } from "./installer/types";
import { EnvironmentVariables } from "./variables/types";


export const IsWindows = Symbol('IS_WINDOWS');
export const IS_WINDOWS = /^win/.test(process.platform);
export type Resource = Uri | undefined;
/**
 * Experiment service leveraging VS Code's experiment framework.
 */
export const IExperimentService = Symbol('IExperimentService');
export interface IExperimentService {
    activate(): Promise<void>;
    inExperiment(experimentName: string): Promise<boolean>;
    inExperimentSync(experimentName: string): boolean;
    getExperimentValue<T extends boolean | number | string>(experimentName: string): Promise<T | undefined>;
}
export interface IPersistentState<T> {
    /**
     * Storage is exposed in this type to make sure folks always use persistent state
     * factory to access any type of storage as all storages are tracked there.
     */
    readonly storage: Memento;
    readonly value: T;
    updateValue(value: T): Promise<void>;
}

export type ReadWrite<T> = {
    -readonly [P in keyof T]: T[P];
};
export const IPersistentStateFactory = Symbol('IPersistentStateFactory');

export interface IPersistentStateFactory {
    createGlobalPersistentState<T>(key: string, defaultValue?: T, expiryDurationMs?: number): IPersistentState<T>;
    createWorkspacePersistentState<T>(key: string, defaultValue?: T, expiryDurationMs?: number): IPersistentState<T>;
}
export const IOutputChannel = Symbol('IOutputChannel');
export interface IOutputChannel extends OutputChannel { }
export const IMemento = Symbol('IGlobalMemento');
export const GLOBAL_MEMENTO = Symbol('IGlobalMemento');
export const WORKSPACE_MEMENTO = Symbol('IWorkspaceMemento');

export const IDisposableRegistry = Symbol('IDisposableRegistry');
export type IDisposableRegistry = Disposable[];

export interface IDisposable {
    dispose(): void | undefined;
}
export interface IAsyncDisposable {
    dispose(): Promise<void>;
}

export const IAsyncDisposableRegistry = Symbol('IAsyncDisposableRegistry');
export interface IAsyncDisposableRegistry extends IAsyncDisposable {
    push(disposable: IDisposable | IAsyncDisposable): void;
}

export const IExtensionContext = Symbol('ExtensionContext');
export interface IExtensionContext extends ExtensionContext { }

export const IPathUtils = Symbol('IPathUtils');
export interface IPathUtils {
    readonly delimiter: string;
    readonly home: string;
    /**
     * The platform-specific file separator. '\\' or '/'.
     * @type {string}
     * @memberof IPathUtils
     */
    readonly separator: string;
    getPathVariableName(): 'Path' | 'PATH';
    basename(pathValue: string, ext?: string): string;
    getDisplayName(pathValue: string, cwd?: string): string;
}

export const ICurrentProcess = Symbol('ICurrentProcess');
export interface ICurrentProcess {
    readonly env: EnvironmentVariables;
    readonly argv: string[];
    readonly stdout: NodeJS.WriteStream;
    readonly stdin: NodeJS.ReadStream;
    readonly execPath: string;
    // eslint-disable-next-line @typescript-eslint/ban-types
    on(event: string | symbol, listener: Function): this;
}

export enum Product {
    pytest = 1,
    pylint = 3,
    flake8 = 4,
    pycodestyle = 5,
    pylama = 6,
    prospector = 7,
    pydocstyle = 8,
    yapf = 9,
    autopep8 = 10,
    mypy = 11,
    unittest = 12,
    isort = 15,
    black = 16,
    bandit = 17,
    jupyter = 18,
    ipykernel = 19,
    notebook = 20,
    kernelspec = 21,
    nbconvert = 22,
    pandas = 23,
    tensorboard = 24,
    torchProfilerInstallName = 25,
    torchProfilerImportName = 26,
    pip = 27,
    ensurepip = 28,
    eq = 29,
}

export interface IFormattingSettings {
    readonly provider: string;
    autopep8Path: string;
    readonly autopep8Args: string[];
    blackPath: string;
    readonly blackArgs: string[];
    yapfPath: string;
    readonly yapfArgs: string[];
}

export interface ITerminalSettings {
    readonly executeInFileDir: boolean;
    readonly focusAfterLaunch: boolean;
    readonly launchArgs: string[];
    readonly activateEnvironment: boolean;
    readonly activateEnvInCurrentTerminal: boolean;
}

export interface IEQSettings {
    readonly defaultInterpreterPath: string;
    readonly eqPath: string;
    readonly formatting: IFormattingSettings;
    readonly terminal: ITerminalSettings;
    initialize(): void;
}
export const IConfigurationService = Symbol('IConfigurationService');
export interface IConfigurationService {
    getSettings(resource?: Uri): IEQSettings;
    isTestExecution(): boolean;
    updateSetting(setting: string, value?: unknown, resource?: Uri, configTarget?: ConfigurationTarget): Promise<void>;
    updateSectionSetting(
        section: string,
        setting: string,
        value?: unknown,
        resource?: Uri,
        configTarget?: ConfigurationTarget,
    ): Promise<void>;
}

export type InterpreterConfigurationScope = { uri: Resource; configTarget: ConfigurationTarget };
export type InspectInterpreterSettingType = {
    globalValue?: string;
    workspaceValue?: string;
    workspaceFolderValue?: string;
};

export const IInterpreterPathService = Symbol('IInterpreterPathService');
export interface IInterpreterPathService {
    onDidChange: Event<InterpreterConfigurationScope>;
    get(resource: Resource): string;
    inspect(resource: Resource): InspectInterpreterSettingType;
    update(resource: Resource, configTarget: ConfigurationTarget, value: string | undefined): Promise<void>;
}

export enum InstallerResponse {
    Installed,
    Disabled,
    Ignore,
}

export enum ProductInstallStatus {
    Installed,
    NotInstalled,
    NeedsUpgrade,
}
export const IInstaller = Symbol('IInstaller');

export interface IInstaller {
    promptToInstall(
        product: Product,
        resource?: InterpreterUri,
        cancel?: CancellationToken,
        flags?: ModuleInstallFlags,
    ): Promise<InstallerResponse>;
    install(
        product: Product,
        resource?: InterpreterUri,
        cancel?: CancellationToken,
        flags?: ModuleInstallFlags,
        options?: InstallOptions,
    ): Promise<InstallerResponse>;
    isInstalled(product: Product, resource?: InterpreterUri): Promise<boolean>;
    isProductVersionCompatible(
        product: Product,
        semVerRequirement: string,
        resource?: InterpreterUri,
    ): Promise<ProductInstallStatus>;
    translateProductToModuleName(product: Product): string;
}

export const IExtensions = Symbol('IExtensions');
export interface IExtensions {
    getExtension(extensionId: string): Extension<unknown> | undefined;
    getExtension<T>(extensionId: string): Extension<T> | undefined;
}
export type DefaultLSType = LanguageServerType.Jedi | LanguageServerType.Node;
export const IDefaultLanguageServer = Symbol('IDefaultLanguageServer');

export interface IDefaultLanguageServer {
    readonly defaultLSType: DefaultLSType;
}

export const IRandom = Symbol('IRandom');
export interface IRandom {
    getRandomInt(min?: number, max?: number): number;
}
export const IBrowserService = Symbol('IBrowserService');
export interface IBrowserService {
    launch(url: string): void;
}

export type ExecutionInfo = {
    execPath?: string;
    moduleName?: string;
    args: string[];
    product?: Product;
    useShell?: boolean;
};

export const IEditorUtils = Symbol('IEditorUtils');
export interface IEditorUtils {
    getWorkspaceEditsFromPatch(originalContents: string, patch: string, uri: Uri): WorkspaceEdit;
}

export const ITerminalServiceFactory = Symbol('ITerminalServiceFactory');

export interface ITerminalService extends IDisposable {
    readonly onDidCloseTerminal: Event<void>;
    /**
     * Sends a command to the terminal.
     *
     * @param {string} command
     * @param {string[]} args
     * @param {CancellationToken} [cancel] If provided, then wait till the command is executed in the terminal.
     * @param {boolean} [swallowExceptions] Whether to swallow exceptions raised as a result of the execution of the command. Defaults to `true`.
     * @returns {Promise<void>}
     * @memberof ITerminalService
     */
    sendCommand(
        command: string,
        args: string[],
        cancel?: CancellationToken,
        swallowExceptions?: boolean,
    ): Promise<void>;
    sendText(text: string): Promise<void>;
    show(preserveFocus?: boolean): Promise<void>;
}

export type TerminalCreationOptions = {
    env?: { [key: string]: string | null };
    resource?: Uri;
    title?: string;
    interpreter?: EQEnvironment;
    hideFromUser?: boolean;
};

export interface ITerminalServiceFactory {
    getTerminalService(options: TerminalCreationOptions): ITerminalService;
    createTerminalService(resource?: Uri, title?: string): ITerminalService;
}